package org.claaswilke.presents.ilp;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import lpsolve.LpSolve;
import lpsolve.LpSolveException;

import org.eclipse.core.resources.ResourcesPlugin;

import de.claaswilke.presents.Conflict;
import de.claaswilke.presents.PastPresent;
import de.claaswilke.presents.PastPresentRule;
import de.claaswilke.presents.Person;
import de.claaswilke.presents.PresentGroup;

/**
 * Helper class to generate and solve ILPs for {@link PresentGroup}s.
 * 
 * @author Claas Wilke
 */
public class IlpGenerator {

	public static Map<String, String> runILP(String ilp) {
		File reqFile = new File("tmp");
		return runILP(reqFile, ilp);
	}

	public static Map<String, String> runILP(File reqFile, String ilp) {

		Map<String, String> ret = new HashMap<String, String>();

		File ilpFile = new File(reqFile.getAbsolutePath() + ".lp");
		System.out.println("ilpFile: " + ilpFile.getAbsolutePath());

		try {
			if (!ilpFile.exists() && !ilpFile.createNewFile())
				System.out.println("cannot create ilp file");
			// no else.

			FileWriter fw = new FileWriter(ilpFile, false);
			fw.write(ilp);
			fw.close();

			System.out.println("written ilp into file: "
					+ ilpFile.getAbsolutePath() + "/" + ilpFile.getName());

			/* Add required DLLs to class path. */
			try {
				String jlp = System.getProperty("java.library.path"); // Windows
				String workspaceLocation = ResourcesPlugin.getWorkspace()
						.getRoot().getLocationURI().getPath();
				if (workspaceLocation.length() > 0) {
					workspaceLocation = workspaceLocation.replaceAll("/",
							"\\\\");
					if (workspaceLocation.startsWith("\\"))
						workspaceLocation = workspaceLocation.substring(1);
					if (!jlp.endsWith(File.pathSeparator))
						jlp += File.pathSeparator;
					jlp += workspaceLocation + File.separator;
				}
				System.setProperty("java.library.path", jlp);
				System.out.println("JLP: "
						+ System.getProperty("java.library.path"));
				LpSolve.lpSolveVersion();

				long start = System.nanoTime();
				LpSolve solver = LpSolve.readLp(ilpFile.getAbsolutePath(), 1,
						ilpFile.getName());
				solver.setBbRule(LpSolve.NODE_PSEUDONONINTSELECT
						+ LpSolve.NODE_RESTARTMODE + LpSolve.NODE_DYNAMICMODE
						+ LpSolve.NODE_RCOSTFIXING);
				solver.solve();
				long duration = System.nanoTime() - start;
				System.out.println("\n====took: " + duration / 1000 / 1000
						+ " ms====");

				System.out.println("Value of objective function: "
						+ solver.getObjective());
				double[] var = solver.getPtrVariables();
				for (int i = 0; i < var.length; i++) {
					if (solver.getColName(i + 1) != null
							&& !solver.getColName(i + 1).startsWith("cost")) {
						if (Math.round(var[i]) == 1) {
							String varName = solver.getColName(i + 1);
							System.out.println("Value of var[" + i + "] ("
									+ varName + ") = " + var[i]);
							ret.put(varName.substring(0, varName.indexOf("_")),
									varName.substring(varName.indexOf("_") + 1));
						}
					}
				}

				// to free memory
				solver.deleteLp();

			} catch (LpSolveException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			}

		} catch (IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		return ret;
	}

	/**
	 * Generates the ILP for a given {@link PresentGroup}.
	 * 
	 * @param group
	 *            The {@link PresentGroup}.
	 * @return The ILP as a {@link String}.
	 */
	public static String generateILP(PresentGroup group) {

		StringBuilder builder = new StringBuilder();

		/* The objective function. */
		builder.append("/* Objective function */\n");
		Iterator<Person> it = group.getPersons().iterator();
		builder.append("min: ");

		while (it.hasNext()) {
			Person person = it.next();
			builder.append("cost_" + person.getIdentifier());

			if (it.hasNext())
				builder.append(" + ");
			else
				builder.append(";\n\n");
		}
		// end while.

		/* Constraints. */
		builder.append("/* Constraints */\n");

		builder.append("/* One customer per guy. */\n");
		for (Person source : group.getPersons()) {

			boolean isFirst = true;

			for (Person target : group.getPersons()) {
				if (!isProhibited(source, target, group.getConflicts())) {
					if (!isFirst)
						builder.append("+ ");
					else
						isFirst = false;
					builder.append(source.getIdentifier() + "_"
							+ target.getIdentifier() + " ");
				}
				// no else.
			}
			// end for.

			builder.append("= 1;\n");
		}
		// end for.

		builder.append("\n/* One present per guy. */\n");
		for (Person target : group.getPersons()) {

			boolean isFirst = true;

			for (Person source : group.getPersons()) {
				if (!isProhibited(source, target, group.getConflicts())) {
					if (!isFirst)
						builder.append("+ ");
					else
						isFirst = false;
					builder.append(source.getIdentifier() + "_"
							+ target.getIdentifier() + " ");
				}
				// no else.
			}
			// end for.

			builder.append("= 1;\n");
		}
		// end for.

		builder.append("\n/* Cost per customer. */\n");

		for (Person from : group.getPersons()) {

			List<PastPresentRule> tos = new ArrayList<PastPresentRule>();

			for (PastPresent pastPresent : group.getPastPresents()) {

				if (pastPresent.getFrom().equals(from))
					tos.addAll(pastPresent.getTo());
				// no else.
			}

			builder.append("cost_" + from.getIdentifier() + " = ");

			if (tos.size() == 0)
				builder.append("0;\n");

			else {
				Iterator<PastPresentRule> it2 = tos.iterator();

				while (it2.hasNext()) {
					PastPresentRule to = it2.next();
					if (null != to.getMultiplicator())
						builder.append(to.getMultiplicator().toString() + " ");
					// no else.
					builder.append(from.getIdentifier() + "_"
							+ to.getPerson().getIdentifier());

					if (it2.hasNext())
						builder.append(" + ");
					else
						builder.append(";\n");
				}
				// end while.
			}
			// end else.
		}
		// end for.

		return builder.toString();
	}

	/**
	 * Helper method that checks whether or not a combination of source and
	 * target is prohibited.
	 * 
	 * @param source
	 *            The source {@link Person}.
	 * @param target
	 *            The target {@link Person}.
	 * @param conflicts
	 *            The specified {@link Conflict}s.
	 * @return If <code>true</code> the combination is prohibited.
	 */
	private static boolean isProhibited(Person source, Person target,
			List<Conflict> conflicts) {

		if (source.equals(target))
			return true;
		else {
			for (Conflict conflict : conflicts) {
				if (conflict.getConflictor().equals(source)
						&& conflict.getConflictees().contains(target))
					return true;
				// no else.
			}
			// end for.
		}

		/* No conflict found. */
		return false;
	}

}
